﻿using Bettzueche.Dampfross;
using Bettzueche.RLLibrary.Types;
using System;
using System.Collections.Generic;
using System.Text;

namespace Bettzueche.DampfrossContinuum
{
    /// <summary>
    /// RL-Zustand. Beinhaltet zentrale und benachbarte Kacheln, Zielrichtung, (Zielentfernung?).
    /// </summary>
    /// <remarks>
    /// * Je Merkmalsdimension gibt es ein Tiling. Die Merkmale werden auf Basis der Zustandsdaten und zughöriger Tilings
    ///   je nach Tiling-Organisation und -Kombination ermittelt (siehe Methode <c>SetFeatures</c>).
    /// * Hashwert wird anhand der Merkmale des Zustands ermittelt. Zustände mit gleichen Merkmalen (nicht Maßen und so)
    ///   haben den gleichen Hashwert (sind gleich).
    /// * WARNUNG: Für FeatureCombi 2 - 4 wurde die Hashfunktion umgeschrieben! Zur reproduktion der älteren Tests
    ///   muss die alte Hashfunktion wieder auskommentiert werden!
    /// </remarks>
    public class UmgebungC : Bettzueche.RLLibrary.Types.IState
    {
        /// <summary>
        /// Merkmalindizes. Die Merkmale, die dieser "Zustand" besitzt.
        /// </summary>
        int[] _merkmale;

        /// <summary>
        /// Nachbarfelder. Sortiert von NordOst gegen Uhrzeigersinn bis SüdOst. Auch Durchlauf dieses Enumerators
        /// </summary>
        public readonly KachelC[] Nachbarn;

        /// <summary>
        /// Die charakterischen Landschaftsmerkmale der Nachbarkacheln und, als letztes Element, 
        /// das des Zentrumfeldes. Wird beim Konstruktor initialisiert.
        /// </summary>
        double[] _measures;

        /// <summary>
        /// Flags für die Flussprüfung bei der Indizierung. Sortiert von NO-Feld gegen Uzg bis SO-Feld.<para>
        /// Hinweis zur Modellierung: Jedes Nachbarfeld hat an der Kante zum Zentrum und an der zum linken Nachbarn im UzgSinn
        /// ev. einen Flussübergang. Dadurch können alle möglichen Flüsse im Sichtfeld angegeben werden.</para>
        /// </summary>
        static readonly Tuple<Rivers, Rivers>[] Flags;


        /// <summary>
        /// The zentrum feld
        /// </summary>
        public readonly KachelC ZentrumFeld;

        // Vector _zielRichtung;
        /// <summary>
        /// Gets the richtung ziel.
        /// </summary>
        /// <value>
        /// The richtung ziel.
        /// </value>
        public HexRichtungen RichtungZiel {
            get;
            private set;
        }

        /// <summary>
        /// Gets the entfernung.
        /// </summary>
        /// <value>
        /// The entfernung.
        /// </value>
        public int Entfernung {
            get;
            private set;
        }

        /// <summary>
        /// Gibt die merkmalsbasierte ID dieses Zustands.
        /// Zwei Zustände haben dieselbe ID, wenn sie dieselben Merkmale besitzen.
        /// Die ID ist 0, wenn noch keine Merkmale gesetzt wurden.
        /// </summary>
        /// <value>
        /// The featured identifier.
        /// </value>
        internal int FeaturedID {
            get;
            private set;
        }

        /// <summary>
        /// Gets the features.
        /// </summary>
        /// <returns></returns>
        internal IEnumerable<int> GetFeatures() {
            if (_merkmale == null) {
                yield break;
            }
            for (int i = 0; i < _merkmale.Length; i++) {
                yield return _merkmale[i];
            }
        }

        /// <summary>
        /// Gets the enumerable measures. Von Nachbar NO guz. bis SO plus Zentrum.
        /// </summary>
        /// <returns>Enumerable measures. Von Nachbar NO guz. bis SO plus Zentrum</returns>
        internal IEnumerable<double> GetMeasures() {
            if (_measures == null) {
                yield break;
            }
            for (int i = 0; i < _measures.Length; i++) {
                yield return _measures[i];
            }
        }

        internal int FeatureCount {
            get {
                if (_merkmale == null)
                    return 0;
                return _merkmale.Length;
            }
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="UmgebungC"/> class
        /// und setzt die Merkmale.
        /// </summary>
        /// <param name="zentrum">The zentrum.</param>
        /// <param name="zielRichtung">The ziel richtung.</param>
        /// <param name="entfernung">The entfernung.</param>
        /// <param name="nachbarn">The nachbarn.</param>
        /// <param name="featureCombi">Unterteilung / Organisation u.a. des Landschaftsmerkmals einer Kachel.</param>
        internal UmgebungC(KachelC zentrum, HexRichtungen zielRichtung, int entfernung, KachelC[] nachbarn, IFeatures<UmgebungC> featureCombi) {
            ZentrumFeld = zentrum;
            RichtungZiel = zielRichtung;
            Entfernung = entfernung;
            Nachbarn = nachbarn;
            BerechneMeasures();
            SetFeatures(featureCombi); // TODO: SetFeatures mit LandschaftsTiling
                                        // Note: Umwelt.ErzeugeSichtfeld ruft diesen Konstruktor auf
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="UmgebungC"/> class
        /// und setzt die Merkmale (anhand des Standard <see cref="LandschaftsTiling" />).
        /// </summary>
        /// <param name="zentrum">The zentrum.</param>
        /// <param name="zielRichtung">The ziel richtung.</param>
        /// <param name="entfernung">The entfernung.</param>
        /// <param name="nachbarn">The nachbarn.</param>
        internal UmgebungC(KachelC zentrum, HexRichtungen zielRichtung, int entfernung, KachelC[] nachbarn) :
            this(zentrum, zielRichtung, entfernung, nachbarn, new FeatureCombi1(new LandschaftsTiling()))
        {

        }

        /// <summary>
        /// Erwartet Array mit Merkmalindizes und, als letztes (!) Element der FeaturedID.
        /// Übernimmt (kopiert) nur diese Werte und setzt sonst keine (!) Eigenschaften, wie z.B. <c>Nachbarn</c>
        /// oder <c>Entfernung</c>!
        /// </summary>
        /// <param name="merkmale">Array mit Merkmal-IDs plus FeatureID (als letztes Element).</param>
        /// <exception cref="System.ArgumentNullException">merkmale</exception>
        /// <exception cref="System.ArgumentException">merkmale array with at least two values expected.</exception>
        /// <remarks>Dieser Konstruktor dient nur der Interface <see cref="IFeatures{SType}"/> Methode <c>GetStateFromFeatures</c>.</remarks>
        internal UmgebungC(int[] merkmale) {
            if (merkmale == null)
                throw new ArgumentNullException("merkmale");
            if (merkmale.Length <= 1)
                throw new ArgumentException("merkmale array with at least two values expected.");
            // der letzte Wert ist die FeaturedID, alle anderen die Merkmale
            _merkmale = new int[merkmale.Length-1];
            FeaturedID = merkmale[merkmale.Length-1];
            Array.Copy(merkmale, _merkmale, _merkmale.Length);
        }

        private UmgebungC() {

        }

        //internal static UmgebungC GetInstanceFromFeatures(int[] merkmale) {
        //    return UmgebungC.GetInstanceFromFeatures(merkmale, new LandschaftsTiling());
        //}
        internal static UmgebungC GetInstanceFromFeatures(int[] merkmale, LandschaftsTiling uTiling) {
            if (merkmale == null)
                throw new ArgumentNullException("merkmale");
            if (merkmale.Length <= 1)
                throw new ArgumentException("merkmale array with at least two values expected.");
            if (uTiling == null)
                throw new ArgumentNullException("uTiling");
            UmgebungC retVal = new UmgebungC();
            // der letzte Wert ist die FeaturedID, alle anderen die Merkmale
            retVal._merkmale = new int[merkmale.Length-1];
            retVal.FeaturedID = merkmale[merkmale.Length-1];
            Array.Copy(merkmale, retVal._merkmale, retVal._merkmale.Length);
            // jetzt umgekehrt aus fID die zugehörigen Objekte erzeugen (siehe GibMerkmalsVektor(state)):
            retVal._measures = new double[7]; // Maße für Nachbarn + Zentrum
            for (int m = 0; m < retVal._merkmale.Length; m++) { // ACHTUNG!!! Mehrere Merkmale noch nicht wirklich unterstützt!! merkmale.Length=1 erwartet
                int fTemp = retVal._merkmale[m];
                // Entfernung
                int offSet = 2 * (int)Math.Pow(uTiling.TileCount, 6) * 6;
                int entfNr = fTemp / offSet;
                var entfDic = EntfernungTiling.GetMedians(new int[] { entfNr });
                retVal.Entfernung = (int)entfDic[entfNr]; // abgerundet
                fTemp = fTemp - (entfNr * offSet);
                // Richtung
                offSet /= 6;
                int dirNum = fTemp / offSet;
                retVal.RichtungZiel = (HexRichtungen)dirNum;
                fTemp = fTemp - (dirNum * offSet);
                // Nachbar SO, S, ..., N, NO
                for (int i = 0; i < 6; i++) {
                    offSet /= uTiling.TileCount;
                    int kachel_iNr = fTemp / offSet;
                    var kachelDic = uTiling.GetMedians(new int[] { kachel_iNr });
                    retVal._measures[i] += kachelDic[kachel_iNr]; // TODO: Bei echtem MultiTiling stattdessen Mittelwert der Mittelwerte?
                    // hier wirds kompliziert.
                    // aus dem measure müssten RiverFlags, Typ und measurebase ermittelt werden...
                    //KachelCContent content_i = new KachelCContent(4.2, Kacheltyp.Berg, Rivers.NordOst | Rivers.Nord);
                    //KachelC kachel_i = new KachelC { Content = content_i };
                    // für state.GetFeatures (wie in QEFunction[state,action] genutzt), reicht es allerdings wenn
                    // _merkmale gesetzt ist!
                    fTemp = fTemp - (kachel_iNr * offSet);
                }
                // Zentrumsfeld
                int zentrumNr = fTemp;
                var ztrmDic = ZentrumTiling.GetMedians(new int[] { zentrumNr });
                retVal._measures[retVal._measures.Length-1] = ztrmDic[zentrumNr];
            }
            for (int i = 0; i < 6; i++) {
                retVal._measures[i] /= retVal._merkmale.Length;
            }
            return retVal;
        }

        static UmgebungC() {
            Flags = new Tuple<Rivers, Rivers>[6]; // 6 Nachbarfelder
            Flags[0] = new Tuple<Rivers, Rivers>(Rivers.SudWest, Rivers.NordWest); // NO
            Flags[1] = new Tuple<Rivers, Rivers>(Rivers.Sud, Rivers.SudWest); // N
            Flags[2] = new Tuple<Rivers, Rivers>(Rivers.SudOst, Rivers.Sud); // NW
            Flags[3] = new Tuple<Rivers, Rivers>(Rivers.NordOst, Rivers.SudOst); // SW
            Flags[4] = new Tuple<Rivers, Rivers>(Rivers.Nord, Rivers.NordOst); // S
            Flags[5] = new Tuple<Rivers, Rivers>(Rivers.NordWest, Rivers.Nord); // SO
        }

        /// <summary>
        /// Setzt (einmal!) die Merkmale des angegebenen Zustands (Mit Standard LandschaftsTiling).
        /// </summary>
        /// <param name="state">The state.</param>
        /// <returns>
        /// true, wenn die Merkmale nicht null sind und zuvor nocht nicht gesetzt wurden.
        /// </returns>
        private bool SetFeatures(UmgebungC state) {
            IFeatures<UmgebungC> fCombi = new FeatureCombi1(new LandschaftsTiling());
            return this.SetFeatures(fCombi);
        }
        /// <summary>
        /// Setzt die Merkmale des angegebenen Zustands.
        /// </summary>
        /// <param name="featureCombi">
        /// Die "Merkmals"-Aufteilung und Organisation des kontinuierlichen Wertebereichs, der u.a. den Landschaftstyp
        /// einer Kachel beschreibt.
        /// </param>
        /// <returns>
        /// true, wenn die Merkmale nicht null sind und zuvor nocht nicht gesetzt wurden.
        /// </returns>
        /// <remarks>
        /// state.FeaturedID ist != 0, wenn Merkmale gesetzt sind.
        /// </remarks>
        private bool SetFeatures(IFeatures<UmgebungC> featureCombi) {
            if (this.Nachbarn == null || this.ZentrumFeld == null || featureCombi == null) {
                return false;
            }
            this._merkmale = featureCombi.GetFeatureSet(this);
            this.FeaturedID = this.GetFeatureHash();
            return true;
        }

        // int[] GibMerkmalsVektor(LandschaftsTiling uTiling) jetzt in FeatureCombi
        ///// <summary>
        ///// Errechnet das Feature Set (Indizes der Merkmale dieses Zustands).
        ///// </summary>
        ///// <param name="uTiling">
        ///// Die "Merkmals"-Aufteilung des kontinuierlichen Wertebereichs, der den Landschaftstyp
        ///// einer Kachel beschreibt.
        ///// </param>
        ///// <returns>
        ///// Merkmalindizes.
        ///// </returns>
        //private int[] GibMerkmalsVektor(LandschaftsTiling uTiling) {
        //    List<int> oobIndizes = new List<int>(); // indizes des Zwischenergebnis-Vektors mit OutOfBounds
        //    int anzNachbarn = Nachbarn.Length; // = config.Abschnitte;
        //    int anzZielrichtungen = Nachbarn.Length; // = config.Zielrichtungen;
        //    double[] unebenheiten = _measures; // = state.Unebenheiten;
            
        //    // Je Tiling eines MultiTiling gibt es ein Merkmal.
        //    // Note: Bei MultiTiling kann es mehrere Merkmale je Zustand geben. Hier nur MultiTiling für LandschaftsTiling.
        //    int[] featureNums = new int[uTiling.TilingCount];
        //    // Beginne mit Unebenheitsmerkmal des Zentrumfeldes:
        //    int ztrmNum   = ZentrumTiling.GetFeatureNumber(unebenheiten[unebenheiten.Length-1]);
        //    // Das ZentrumTiling gibt nur ein Merkmal - es ist ein Single-Tiling. Der Wert muss
        //    // auf alle Merkmale des FeatVec angewendet werden:
        //    if (ztrmNum == Tiling.OutOfBounds)
        //        return new int[0];
        //    for (int i = 0; i < featureNums.Length; i++) {
        //        featureNums[i] = ztrmNum;
        //    }
        //    int indexOffset = ZentrumTiling.TileCount / ZentrumTiling.TilingCount; // erster OffsetFaktor = Anzahl möglicher Merkmale für Zentrum = 2
        //    int anzUIntervalleJeTiling = uTiling.TileCount / uTiling.TilingCount;
        //    // zu jedem Nachbar-Abschnitt Unebenheits-Merkmale ermitteln
        //    for (int abschnittsNr = 0; abschnittsNr < anzNachbarn; abschnittsNr++) {
        //        // Merkmale gemäß Tilings-Organisation:
        //        double u_ab = unebenheiten[abschnittsNr];
        //        int[] merkmale_u_ab = uTiling.GetFeatureNumbers(u_ab);
        //        // MerkmalIndizes anpassen gemäß Merkmale je Abschnitt:
        //        for (int i = 0; i < merkmale_u_ab.Length; i++) {
        //            if (featureNums[i] == Tiling.OutOfBounds)
        //                continue;
        //            if (merkmale_u_ab[i] == Tiling.OutOfBounds) {
        //                featureNums[i] = Tiling.OutOfBounds;
        //                oobIndizes.Add(i);
        //            }
        //            else {
        //                merkmale_u_ab[i] *= indexOffset;
        //                featureNums[i] += merkmale_u_ab[i];
        //            }
        //        }
        //        indexOffset *= uTiling.TileCount/*anzUIntervalleJeTiling*/; // Offset für nächste Kachel mit Anz-U-Werte multiplizieren, ich glaub das ist falsch! muss * TileCount
        //    }
        //    // Merkmal Richtung (einmal in Kombination mit jedem uTiling)
        //    int richtungNr = (int)RichtungZiel;
        //    richtungNr *= indexOffset;
        //    for (int i = 0; i < featureNums.Length; i++) {
        //        if (featureNums[i] != Tiling.OutOfBounds) {
        //            featureNums[i] += richtungNr;
        //        }
        //    }
        //    indexOffset *= 6; // Anzahl Richtungen = 6; 1 "Tiling" 

        //    // Merkmal Entfernung; auch hier nur 1 "Tiling", kombiniert mit jedem uTiling
        //    int entfNr = EntfernungTiling.GetFeatureNumber(Entfernung);
        //    if (entfNr == Tiling.OutOfBounds)
        //        return new int[0];
        //    entfNr *= indexOffset;
        //    for (int i = 0; i < featureNums.Length; i++) {
        //        if (featureNums[i] != Tiling.OutOfBounds) {
        //            featureNums[i] += entfNr;
        //        }
        //    }
        //    //indexOffset *= EntfernungTiling.TileCount / EntfernungTiling.TilingCount;

        //    // falls ein IndexOutOfBounds vorhanden, Ergebnisvektor reduzieren:
        //    if (oobIndizes.Count > 0) {
        //        List<int> resultVec = new List<int>(featureNums.Length);
        //        for (int i = 0; i < featureNums.Length; i++) {
        //            if (featureNums[i] != Tiling.OutOfBounds) {
        //                resultVec.Add(featureNums[i]);
        //            }
        //        }
        //        return resultVec.ToArray();
        //    }
        //    else
        //        return featureNums;
        //}


        /// <summary>
        /// Initialisierungsmethode für den Konstruktor.
        /// <para>
        /// Es wird vorausgesetzt, dass eine Kachel einen reellen, charakteristischen Basiswert zwischen 0.0 und 0.1 hat.
        /// Abhängig von Landschaftstyp und Flussverläufen (aus Sicht des Zentrums) wird der aktuelle Wert jeder Kachel für
        /// diesen Zustand berechnet und im Feld <c>_measures</c> festgehalten.</para>
        /// <para>
        /// Diese Werte sind vergleichbar mit den Unebenheitsmaßen im AMEE-Szenario und werden zur Berechnung des Feature Sets
        /// (Thema MultiTiling) herangezogen.</para>
        /// </summary>
        private void BerechneMeasures() {
            _measures = new double[Nachbarn.Length + 1]; // Maße für Nachbarn + Zentrum
            // Zentrumsfeld
            double addWasser = 0.8;
            double addBerg = 0.4;
            
            switch (ZentrumFeld.Content.Typ) {
                case Kacheltyp.Wasser:
                    _measures[_measures.Length - 1] = ZentrumFeld.Content.MeasureBase + addWasser;
                    break;
                case Kacheltyp.Ebene:
                    _measures[_measures.Length - 1] = ZentrumFeld.Content.MeasureBase;
                    break;
                case Kacheltyp.Berg:
                    _measures[_measures.Length - 1] = ZentrumFeld.Content.MeasureBase + addBerg;
                    break;
                case Kacheltyp.Stadt:
                    _measures[_measures.Length - 1] = ZentrumFeld.Content.MeasureBase;
                    break;
                default: // theoretisch unbekannter Typ
                    _measures[_measures.Length - 1] = ZentrumFeld.Content.MeasureBase + 0.9;
                    break;
            }

            // Felder NO bis SO
            // zum charakterischen Boden-Grund-Maß (Wert zwischen 0 und 0.1) wird
            //   1. Abhängig vom Landschaftstyp ein Wert hinzuaddiert
            //   2. Abhängig von Flussverläufen aus der Zentrumsperspektive hinzuaddiert
            // um das endgültige, perspektivenabhängige Bodencharakteristikum zu der Nachbarfelder
            // zu erhalten.
            for (int i = 0; i < Nachbarn.Length; i++) {
                KachelC kachel = Nachbarn[i];
                switch (kachel.Content.Typ) {
                    case Kacheltyp.Wasser:
                        _measures[i] = kachel.Content.MeasureBase + addWasser;
                        break;
                    case Kacheltyp.Berg:
                        _measures[i] = kachel.Content.MeasureBase + addBerg;
                        break;
                    default:
                        _measures[i] = kachel.Content.MeasureBase;
                        break;
                }
                _measures[i] += GetMeasureRiverAddOn(kachel, i); // Flüsse berücksichtigen
            }

        }

        /// <summary>
        /// Gibt den zum Landschaftsmaß zu addierenden Aufschlag für ggf. vorhandene Flüsse.<para>
        /// Fluss zum linken Nachbars-Nachbarn 'kostet' 0.1, zum Zentrum 0.2, beides 0.3.
        /// </para>
        /// </summary>
        /// <param name="ngbKachel">The neighbour kachel.</param>
        /// <param name="neighbourIdx">Index of the neighbour kachel.</param>
        /// <returns>{0.0, 0.1, 0.2, 0.3}; Den zum Landschaftsmaß zu addierenden Aufschlag für ggf. vorhandene Flüsse.</returns>
        public double GetMeasureRiverAddOn(KachelC ngbKachel, int neighbourIdx) {
            double linksAdd = 0.1;
            double zentrumAdd = 0.2;
            double liZenAdd = 0.3;
            Rivers flusse = ngbKachel.Content.FlussFlags & (UmgebungC.Flags[neighbourIdx].Item1 | UmgebungC.Flags[neighbourIdx].Item2);
            if (flusse == Flags[neighbourIdx].Item1) {
                return linksAdd; // Fluss zum Zentrum
            }
            else if (flusse == Flags[neighbourIdx].Item2) {
                return zentrumAdd; // Fluss zum linken Nachbarn
            }
            else if (flusse == (Flags[neighbourIdx].Item1 | Flags[neighbourIdx].Item2)) {
                return liZenAdd; // Fluss zum Zentrum und linken Nachbarn
            }
            else {
                return 0; // ohne Fluss
            }
        }

        // WARNUNG an mich:
        // diese "alte" Featureberechnung ist ungünstig für die FeatureCombi 2 bis 4
        // alle anderen, älteren Tests sind mit FeatureCombi1 und höchstwahrscheinlich nur mit dieser
        // HashFunktion reproduzierbar
        ///// <summary>
        ///// Helper, um die MerkmalsID zu setzen.
        ///// </summary>
        ///// <returns></returns>
        //private int GetFeatureHash() {
        //    if (_merkmale == null)
        //        return Int32.MinValue;
        //    if (_merkmale.Length == 0)
        //        return -1;
        //    int hc = _merkmale[0];
        //    for (int i = 1; i < _merkmale.Length; i++) {
        //        hc = (hc << 0x10) ^ _merkmale[i];
        //    }
        //    // Quatsch. Entfernung und Zielrichtung gehören mit zu den Merkmalen und sind
        //    // daher bereits im Array _merkamle berücksichtigt.
        //    //hc = hc * 7 + this.Entfernung;
        //    //hc = hc *13 + (int)this.RichtungZiel;
        //    return hc;
        //}

        /// <summary>
        /// Helper, um die MerkmalsID zu setzen.
        /// </summary>
        /// <returns></returns>
        private int GetFeatureHash() {
            if (_merkmale == null)
                return Int32.MinValue;
            if (_merkmale.Length == 0)
                return -1;
            int hc = 11;
            for (int i = 0; i < _merkmale.Length; i++) {
                hc = 31 * hc + _merkmale[i];
            }
            return hc;
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode() {
            if (this.FeaturedID == 0)
                return GetFeatureHash();
            return FeaturedID;
        }

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String" /> that represents this instance.
        /// </returns>
        public override string ToString() {
            StringBuilder retVal = new StringBuilder("UmgebungC(");
            retVal.AppendFormat("ID:{0}", this.FeaturedID);
            if (_merkmale != null) {
                retVal.Append("{");
                foreach (var item in _merkmale) {
                    retVal.Append(",").Append(item);
                }
                retVal.Append("}");
            }
            retVal.Append(")");
            return retVal.ToString();
        }
    }
}
